package emergency;

import java.util.ArrayList;

import unit.Ambulance;
import unit.FireTruck;
import unit.Garage;
import unit.PoliceCar;
import unit.Unit;

import movement.Location;

import dispatch.*;

/**
 * A class for creating a new Emergency
 */
@SuppressWarnings("unused")
public abstract class Emergency {

	/**
	 * Variable registering the location where the Emergency occurred
	 */
	private Location location;

	/**
	 * Variable registering the severity of this Emergency
	 */
	private Severity severity;

	/**
	 * Variable registering the amount of hours before this emergency started
	 */
	private int startTimeHours;

	/**
	 * Variable registering the amount of minutes before this emergency started
	 */
	private int startTimeMinutes;

	/**
	 * Variable registering the units currently assigned to this Emergency
	 */
	private ArrayList<Unit> units;

	/**
	 * Variable registering if the emergency is part of a disaster
	 */
	private Disaster disaster = null;

	/**
	 * Variable that contains all the requirements an emergency needs
	 */
	private RequirementsEmergency emergencyRequirements;

	/**
	 * Variable registering the current state of this Emergency
	 */
	private State state;

	/**
	 * Variable registering the current state of this Emergency
	 */
	private EmergencyPolicy emergencyPolicy;

	/**
	 * Initialize this new Emergency with a given location and severity
	 * 
	 * @param location
	 *            The location where this Emergency occurred
	 * 
	 * @param severity
	 *            The severity of this Emergency
	 * 
	 * @param dispatchcenter
	 *            The DispatchCenter where this emergency has to be added to
	 * 
	 * @param emPolicy
	 *            The emergency policy of this Emergency
	 * 
	 * @post This Emergency his location is set to the given location
	 * 
	 * @post This Emergency his severity is set to the given severity
	 * 
	 * @post This Emergency his assigned units is instantiated and empty (no
	 *       units assigned)
	 * 
	 * @post The state of this emergency is set as Recorded
	 * 
	 * @post The emergencyPolicy of this Emergency is set to the given policy
	 * 
	 * @post The emergency requirements are created and set
	 * 
	 * @post Disaster is set
	 * 
	 * @post The emergency is added to the given dispatchcenter
	 * 
	 * @post The starttime of this Emergency is set
	 * */
	public Emergency(Location location, Severity severity,
			DispatchCenter dispatchCenter, EmergencyPolicy emPolicy) {

		setState(new StateRecorded());
		setLocation(location);
		setSeverity(severity);
		setDisaster(null);
		units = new ArrayList<Unit>();
		addEmergencyToDispatchCenter(dispatchCenter);
		setEmergencyPolicy(emPolicy);

		RequirementsLocalFire localFireRequirements = new RequirementsLocalFire();
		RequirementsHouseFire houseFireRequirements = new RequirementsHouseFire();
		RequirementsFacilityFire facilityFireRequirements = new RequirementsFacilityFire();
		RequirementsTrafficAccident trafficAccidentRequirements = new RequirementsTrafficAccident();
		RequirementsRobbery robberyRequirements = new RequirementsRobbery();
		RequirementsPublicDisturbance publicDisturbanceRequirements = new RequirementsPublicDisturbance();
		RequirementsDisaster disasterRequirements = new RequirementsDisaster();
		RequirementDefaultEmergency defaultEmergencyRequirement = new RequirementDefaultEmergency();

		localFireRequirements.setNextInChain(houseFireRequirements);
		houseFireRequirements.setNextInChain(facilityFireRequirements);
		facilityFireRequirements.setNextInChain(trafficAccidentRequirements);
		trafficAccidentRequirements
				.setNextInChain(publicDisturbanceRequirements);
		publicDisturbanceRequirements.setNextInChain(robberyRequirements);
		robberyRequirements.setNextInChain(disasterRequirements);
		disasterRequirements.setNextInChain(defaultEmergencyRequirement);

		setEmergencyRequirements(localFireRequirements);
		this.startTimeHours = dispatchCenter.getClock().getHours();
		this.startTimeMinutes = dispatchCenter.getClock().getMinutes();
	}

	/**
	 * Method for adding this Emergency to the EmergencyBoard of the given
	 * DispatchCenter
	 * 
	 * @param center
	 *            The dispatchCenter where we need to add the emergency to the
	 *            dispatchCenter his emergencyBoard
	 * 
	 * @post This emergency is added to the given dispatchCenter it's
	 *       emergencyBoard
	 * 
	 * @throws IllegalArgumentException
	 *             This emergency can't be added an invalid dispatchCenter
	 * 
	 */
	private void addEmergencyToDispatchCenter(DispatchCenter center) {
		try {
			center.addEmergencyToDispatchCenter(this);
		} catch (Exception e) {
			throw new IllegalArgumentException(
					"Invalid dispatchcenter to add the emergency to");
		}
	}

	/**
	 * Assign all the available units of this garage needed for a given
	 * emergency to this emergency
	 * 
	 * @param garage
	 *            The garage you want to use to search units , needed for this
	 *            emergency
	 * 
	 * @post the needed units are assigned to this emergency
	 * 
	 * @post the needed units are send to this emergency it's location, after
	 *       assigning the last unit
	 * 
	 * @throws IllegalAccessException
	 *             , if the emergency is part of a disaster ( automatic
	 *             scheduling )
	 * 
	 * @throws IllegalArgumentException
	 *             , if this emergency doesn't have needed units
	 */
	public void assignAllUnits(Garage garage) throws IllegalAccessException {
		if (getDisaster() == null) {
			ArrayList<Unit> unitsToAssign = getSuggestion(garage);
			if (unitsToAssign.size() == 0) {
				throw new IllegalAccessException(
						"This emergency doesn't need any more units ");
			}

			for (Unit u : unitsToAssign)
				u.assignEmergency(this);

		} else
			throw new IllegalAccessException(
					"This emergency is part of a disaster and cannot be assigned by a dispatcher!");
	}

	/**
	 * Method for assign the given unit to this Emergency
	 * 
	 * @param u
	 *            The unit that's going to be assigned to this emergency
	 * 
	 * @post if the given unit can be assigned, it is added to this Emergency
	 * 
	 * @throws IllegalArgumentException
	 *             if the given unit is null or if the binding isn't correct
	 * 
	 */
	public void assignUnit(Unit u) {
		if (u.canBeAssigned(this)) {
			getUnits().add(u);
			getEmergencyRequirements().setRequirements(this);
		}

		if (getTotalUnitsNeeded() == 0)
			sendUnits();

	}

	/**
	 * Method to cancel this emergency, depending in which state this emergency
	 * occurs
	 * 
	 * @post This emergency is canceled, or an error handles the problem
	 * 
	 */
	private void cancel() {
		getState().cancel(this);
	}

	/**
	 * Method for cancelling this emergency
	 * 
	 * @post All the units of this emergency are revoked
	 * 
	 * @post The emergency goes to cancelled state
	 * 
	 * @return Returns an empty list when successfully canceled, list with not
	 *         canceled when not
	 */
	public ArrayList<String> cancelEmergency() {
		ArrayList<String> notCancelled = new ArrayList<String>();

		for (Unit u : this.getUnits()) {
			try {
				u.revokeUnit();
			} catch (IllegalAccessException e1) {
				notCancelled.add(e1.toString());
			}
		}

		if (this.getUnits().size() == 0) {
			this.cancel();
			return null;
		} else {
			return notCancelled;
		}
	}

	/**
	 * Method to complete this emergency, depending in which state this
	 * emergency occurs
	 * 
	 * @post This emergency is complete, or an error handles the problem
	 * 
	 */
	public void complete() {
		getState().complete(this);
	}

	/**
	 * Method to get the available units for this emergency
	 * 
	 * @param units
	 *            All the units, ordered according the emergency policy
	 * 
	 * @return Returns a list with a suggestion about which units to assign on
	 *         this emergency
	 */
	public ArrayList<Unit> createSuggestion(ArrayList<Unit> units) {
		return getEmergencyRequirements().createSuggestion(this, units);
	}

	/**
	 * Method for filtering the units, using the emergency policy
	 * 
	 * @param garage
	 *            The garage where you want to filter the units of
	 * 
	 * @return Returns a list of units, ordered depending on the emergency
	 *         policy of this emergency
	 * 
	 */
	@SuppressWarnings("unchecked")
	public ArrayList<Unit> filterUnits(Garage garage) {
		ArrayList<Unit> clone = (ArrayList<Unit>) garage.getAvailableUnits()
				.clone();

		ArrayList<Unit> result = getEmergencyPolicy().sortUnits(clone, this);
		return result;
	}

	/**
	 * Gives a string representation of the description (only used for
	 * disasters)
	 * 
	 * @return Returns a string representation of the description (only used for
	 *         disasters)
	 */
	public String getDescription() {
		return null;
	}

	/**
	 * Getter for getting the current disaster of the emergency
	 * 
	 * @return the disaster where the emergency is part of (null if no disaster)
	 */
	public Disaster getDisaster() {
		return disaster;
	}

	/**
	 * Method for getting a cloned list of the emergencies ( disaster )
	 * 
	 * @return Returns a cloned list of the emergencies ( disaster )
	 */
	public ArrayList<Emergency> getEmergenciesClone() {
		return null;
	}

	/**
	 * Getter for getting the current emergency policy
	 * 
	 * @return the current emergency policy of this emergency
	 */
	private EmergencyPolicy getEmergencyPolicy() {
		return emergencyPolicy;
	}

	/**
	 * Getter for getting a string representation of the current emergency
	 * policy of this emergency
	 * 
	 * @return Returns a string representation of the current emergency policy
	 *         of this emergency
	 */
	public String getEmergencyPolicyString() {
		return getEmergencyPolicy().toString();
	}

	/**
	 * Getter for getting the current emergency requirement of this emergency
	 * 
	 * @return the emergency requirement of this emergency
	 */
	protected RequirementsEmergency getEmergencyRequirements() {
		return emergencyRequirements;
	}

	/**
	 * Getter for getting the location of this Emergency
	 * 
	 * @return Returns the location of this Emergency
	 */
	public Location getLocation() {
		return location;
	}

	/**
	 * Method for getting the current emergency requirement
	 * 
	 * @return Returns the current emergency requirement
	 */
	public RequirementsEmergency getRequirementsEmergency() {
		return emergencyRequirements;
	}

	/**
	 * Getter for getting the severity of this Emergency
	 * 
	 * @return Returns the severity of this Emergency
	 */
	public Severity getSeverity() {
		return this.severity;
	}

	/**
	 * Getter for getting the hours of the start time
	 * 
	 * @return Returns the hours of the start time
	 */
	public int getStartTimeHours() {
		return startTimeHours;
	}

	/**
	 * Getter for getting the minutes of the start time
	 * 
	 * @return Returns the minutes of the start time
	 */
	public int getStartTimeMinutes() {
		return startTimeMinutes;
	}

	/**
	 * Getter for getting the current state of the emergency
	 * 
	 * @return Returns the state of this emergency
	 */
	public State getState() {
		return this.state;
	}

	/**
	 * Getter for getting the current string representation of in which state
	 * the emergency occurs
	 * 
	 * @return Returns a string representation of the state
	 */
	public String getStateName() {
		return getState().toString();
	}

	/**
	 * Method for getting the suggestion , based on emergency policy and
	 * emergency requirements
	 * 
	 * @param garage
	 *            The garage where we gonna use units from
	 * 
	 * @return Returns a list of the suggested units
	 */
	public ArrayList<Unit> getSuggestion(Garage garage) {
		ArrayList<Unit> sortedUnits = (this.filterUnits(garage));
		ArrayList<Unit> unitsToAssign = this.createSuggestion(sortedUnits);

		return unitsToAssign;
	}

	/**
	 * Returns the total amount of ambulances needed to handle this emergency
	 * 
	 * @return Returns the total amount of ambulances needed to handle this
	 *         emergency
	 */
	public int getTotalAmbulancesNeeded() {
		return getRequirementsEmergency().getTotalAmbulancesNeeded();
	}

	/**
	 * Returns the total amount of firetrucks needed to handle this emergency
	 * 
	 * @return Returns the total amount of firetrucks needed to handle this
	 *         emergency
	 */
	public int getTotalFireTrucksNeeded() {
		return getRequirementsEmergency().getTotalFireTrucksNeeded();
	}

	/**
	 * Returns the total amount of policecars needed to handle this emergency
	 * 
	 * @return Returns the total amount of policecars needed to handle this
	 *         emergency
	 */
	public int getTotalPoliceCarsNeeded() {
		return getRequirementsEmergency().getTotalPoliceCarsNeeded();
	}

	/**
	 * Getter for getting the total amount of units needed for this emergency
	 * 
	 * @return Returns the total amount of units needed for this emergency
	 */
	public int getTotalUnitsNeeded() {
		return getEmergencyRequirements().getTotalUnitsNeeded();
	}

	/**
	 * Getter for getting the units currently assigned to this Emergency
	 * 
	 * @return Returns the units currently assigned to this Emergency
	 */
	public ArrayList<Unit> getUnits() {
		return units;
	}

	/**
	 * Getter for getting the units currently assigned to this Emergency
	 * 
	 * @return Returns the (cloned) units currently assigned to this Emergency
	 */
	@SuppressWarnings("unchecked")
	public ArrayList<Unit> getUnitsClone() {
		return (ArrayList<Unit>) units.clone();
	}

	/**
	 * Method for checked the given Location is valid
	 * 
	 * @param location
	 *            The location which need to be checked for validity
	 * 
	 * @return Returns true if the given location is valid, false if not
	 */
	private boolean isValidLocation(Location location) {
		return (location != null);
	}

	/**
	 * Method for checked the given Severity is valid
	 * 
	 * @param severity
	 *            The severity which need to be checked for validity
	 * 
	 * @return Returns true if the given severity is valid, false if not
	 */
	private boolean isValidSeverity(Severity severity) {
		return (severity == Severity.BENIGN || severity == Severity.NORMAL
				|| severity == Severity.SERIOUS || severity == Severity.URGENT);
	}

	/**
	 * Method to set the target location of the needed units to the emergency
	 * it's location
	 * 
	 * @post The target location of all the needed units is set to the location
	 *       of this emergency
	 */
	protected void moveUnits() {
		for (Unit u : getUnits())
			u.setTargetLocation(getLocation());

	}

	/**
	 * Methode for removing the given unit from this emergency
	 * 
	 * @param unit
	 *            , the unit you want to remove
	 * 
	 * @post The given unit is removed from this emergency it's list
	 */
	public void removeUnit(Unit unit) {
		getUnits().remove(unit);
	}

	/**
	 * Method to revoke the units of this emergency
	 * 
	 * @post The needed units for this emergency are revoked
	 * 
	 * @throws IllegalAccessError
	 *             , if the unit can't be revoked
	 */
	protected void revokeUnits() {
		for (Unit u : this.getUnits()) {
			try {
				u.revokeUnit();
			} catch (IllegalAccessException e) {
				throw new IllegalAccessError("Unit can't be revoked");
			}
		}
	}

	/**
	 * Method to send the units of this emergency, depending in which state this
	 * emergency occurs
	 * 
	 * @post The units are sent, or an error handles the problem
	 * 
	 */
	public void sendUnits() {
		getState().sendUnits(this);
	}

	/**
	 * Method for setting the disaster of this emergency
	 * 
	 * @param disaster
	 *            The disaster of which the emergency is part of (null if no
	 *            disaster)
	 * 
	 * @post This emergency is part of the given disaster, or not
	 */
	protected void setDisaster(Disaster disaster) {
		this.disaster = disaster;
	}

	/**
	 * Setter for changing the emergency policy of this emergency
	 * 
	 * @param emergencyPolicy
	 *            The emergency policy you want to set for this emergency
	 * 
	 * @post The emergency policy of this emergency are set to the given
	 *       emergency requirements
	 */
	public void setEmergencyPolicy(EmergencyPolicy emergencyPolicy) {
		this.emergencyPolicy = emergencyPolicy;
	}

	/**
	 * Setter for changing the emergency requirements of this emergency
	 * 
	 * @param emergencyRequirements
	 *            The emergency requirements you want to set for this emergency
	 * 
	 * @post the emergency requirements of this emergency are set to the given
	 *       emergency requirements
	 * 
	 * @throws IllegalArgumentException
	 *             if the given emergency requirement is null
	 */
	protected void setEmergencyRequirements(
			RequirementsEmergency emergencyRequirements) {
		if (emergencyRequirements != null)
			this.emergencyRequirements = emergencyRequirements;
		else
			throw new IllegalArgumentException(
					"Emergencyrequirements cannot be null");

	}

	/**
	 * Method for setting the location of this emergency
	 * 
	 * @param location
	 *            The location of this emergency
	 * 
	 * @post The location of this Emergency is set to the given location
	 * 
	 * @throws IllegalPartnerException
	 *             This Emergency cannot have an invalid location
	 */
	private void setLocation(Location location) {
		if (!isValidLocation(location))
			throw new IllegalArgumentException("Location may not be null");
		this.location = location;
	}

	/**
	 * Method for setting the amount of policecars this Emergency needs
	 * 
	 * @param severity
	 *            The severity of this Emergency
	 * 
	 * @post if the severity is valid the severity of this Emergency is set to
	 *       the given severity else not
	 * 
	 * @throws IllegalPartnerException
	 *             This Emergency cannot have an invalid severity
	 */
	private void setSeverity(Severity severity) throws IllegalArgumentException {
		if (!isValidSeverity(severity))
			throw new IllegalArgumentException("Severity cannot be found!");
		this.severity = severity;
	}

	/**
	 * Method for changing the state of this emergency
	 * 
	 * @param state
	 *            The state you want to set for this emergency
	 * 
	 * @post the state of this emergency are set to the given state
	 */
	protected void setState(State state) {
		this.state = state;
	}

	/**
	 * Method for setting the units assigned to this emergency
	 * 
	 * @param units
	 *            The units assigned to this emergency
	 * 
	 * @post The units assigned to this Emergency are set to the given units
	 * 
	 * @throws IllegalArgumentException
	 *             if the given list with units is null (an empty list is
	 *             allowed)
	 */
	private void setUnits(ArrayList<Unit> units) {
		if (units != null)
			this.units = units;
		else
			throw new IllegalArgumentException(
					"The list of units of an emergency may not be null (empty list is allowed)");
	}

	/**
	 * Method for creating an description of this Emergency
	 * 
	 * @return Returns the description of this Emergency
	 */
	public String toString() {
		return "Unknown emergency";
	}

	/**
	 * Method for reporting the Emergency a unit broke down.
	 * 
	 * @post if a new unit of same type is available, the unit is removed from
	 *       this emergency it's unit's and the state of the emergency is
	 *       changed to recorded
	 * 
	 * @param unit
	 *            , the unit that broke down
	 * 
	 * @throws IllegalArgumentException
	 *             , if the given unit is not broken ( can't on this place )
	 */
	public void unitBrokeDown(Unit unit) throws IllegalArgumentException {
		if (unit.isBroken()) {
			getUnits().remove(unit);
			setState(new StateRecorded());
		} else
			throw new IllegalArgumentException(
					"A unit can't break down at this place");
	}

	/**
	 * Method for checking what the given unit has to do after completing it's
	 * current emergency
	 * 
	 * @param unit
	 *            , the unit you want to see, what he has to do next
	 * 
	 * @post The unit is revoked, or the unit is re-assigned to another
	 *       emergency in a disaster
	 */
	public void unitCompletedEmergency(Unit unit) {
		unit.forceRevokeUnit();
		if (getUnitsClone().size() == 0)
			complete();

		if (getDisaster() != null) {
			Disaster d = getDisaster();
			if (d.allEmergenciesCompleted()) {
				d.revokeUnits();
				d.complete();
			} else {
				if(d.getUnitsToRevoke().contains(unit)){
					try{
						unit.revokeUnit();
						d.getUnitsToRevoke().remove(unit);
					}
					catch(Exception e){}
				}
				else{
				unit.assignEmergency(getDisaster());
				getDisaster().dispatchUnits();
				}
			}
		}
	}

}